#include "stdafx.h"
#include "mapgenerator.hpp"
#include <data/map/ieditableglobalmap.hpp>
#include <data/map/ieditabletile.hpp>
#include <util/vec4.h>

#include <gsl/gsl_errno.h>
#include <gsl/gsl_spline.h>

namespace Demo {

class TileGenerator
{
public:
    static const uint SPLINE_SIZE = 4;

    TileGenerator()
    {
        _spline = gsl_spline_alloc(gsl_interp_cspline_periodic, SPLINE_SIZE);
        _accelerator = gsl_interp_accel_alloc();
    }

    ~TileGenerator()
    {
        delete _spline;
        delete _accelerator;
    }

//#define hat(px, py) heightMap[(px + width * py) >= heightMap.size()
//    ? 0 : (px + width * py)]

#define hat(px, py) heightMap[(px > width ? 0 : px) + \
    (width + 1) * (py > height ? 0 : py)]

    void generateKeyPoints(uint x, uint y, uint width, uint height,
                           const std::vector<float> &heightMap)
    {
        _keyPoints[0]  = hat(x-1, y-1);
        _keyPoints[1]  = hat(x  , y-1);
        _keyPoints[2]  = hat(x+1, y-1);
        _keyPoints[3]  = hat(x+2, y-1);
        _keyPoints[4]  = hat(x-1, y  );
        _keyPoints[5]  = hat(x  , y  );
        _keyPoints[6]  = hat(x+1, y  );
        _keyPoints[7]  = hat(x+2, y  );
        _keyPoints[8]  = hat(x-1, y+1);
        _keyPoints[9]  = hat(x  , y+1);
        _keyPoints[10] = hat(x+1, y+1);
        _keyPoints[11] = hat(x+2, y+1);
        _keyPoints[12] = hat(x-1, y+2);
        _keyPoints[13] = hat(x  , y+2);
        _keyPoints[14] = hat(x+1, y+2);
        _keyPoints[15] = hat(x+2, y+2);
    }

    void calculateTilePoints(Data::IEditableTile &tile)
    {
        static const uint FREQ = Data::ITile::KEYPOINTS_FREQUENCE;
        const double STEP = 1.0 / static_cast<double>(FREQ - 1);

        for (uint xi = 0; xi < FREQ; ++xi)
            for (uint yi = 0; yi < FREQ; ++yi) {
                double height = getExactHeight(STEP * xi, STEP * yi);
                tile.setKeyPoint(xi + FREQ * yi, height);
            }
    }

#undef hat

private:
    inline double getExactHeight(double x, double y)
    {
        vec4d data;
        vec4d final;
        for (uint yidx = 0; yidx < 4; ++yidx) {
            for (uint index = 0; index < 4; ++index)
                data.set(index, _keyPoints[yidx + 4 * index]);
            final.set(yidx, cubicSpline(data, y));
        }
        return cubicSpline(final, x);
    }

    inline double cubicSpline(const vec4d &data, double arg) const
    {
        double x[SPLINE_SIZE] = {-1.0, 0.0, 1.0, 2.0};
        gsl_spline_init(_spline, x, data, SPLINE_SIZE);
        return gsl_spline_eval(_spline, arg, _accelerator);
    }

    gsl_spline *_spline;
    gsl_interp_accel *_accelerator;
    float _keyPoints[16];
};

MapGenerator::MapGenerator(const MapGeneratorOptions &options)
    :_options(options)
    ,_tileGen(new TileGenerator())
{
}

MapGenerator::~MapGenerator()
{
    delete _tileGen;
}

void MapGenerator::generate(Data::IEditableGlobalMap &map) const
{
    map.create(_options.width, _options.height);

    if (_options.landscapeMethod == MapGeneratorOptions::LandscapeAprilRandom)
        generateHeightMapDeprecated(map);
}

void MapGenerator::generateHeightMapDeprecated(Data::IEditableGlobalMap &map) const
{
    srand(static_cast<unsigned>(time(NULL)));
    std::vector<float> heightMap((_options.width+1) * (_options.height+1));
    for (size_t i = 0; i < heightMap.size(); ++i)
    {
        heightMap[i] = 0.3 * static_cast<float>(abs((rand() * 317 - 1281) % 2));
    }
    for (uint x = 0; x < _options.width; ++x)
        for (uint y = 0; y < _options.height; ++y) {
            _tileGen->generateKeyPoints(x, y,
                                        _options.width,
                                        _options.height,
                                        heightMap);
            _tileGen->calculateTilePoints(map.editableCell(x, y).editableTile());
        }
}

} // namespace Demo
